﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Data.Schema.ScriptDom.Sql;
using Microsoft.Data.Schema.ScriptDom;
using System.IO;
using Microsoft.CSharp;
using System.CodeDom.Compiler;

namespace SQLite2CELib
{
    public struct ViewInfo : IEquatable<ViewInfo>
    {
        public String ViewQuery { get; set; }
        public String ViewName { get; set; }

        public override bool Equals(object obj)
        {
            if (!(obj is ViewInfo))
                return false;

            return Equals((ViewInfo)obj);
        }

        public bool Equals(ViewInfo other)
        {
            return (other.ViewName == this.ViewName && other.ViewQuery == this.ViewQuery) ;
        }    
    }

    public struct ViewCodeGenCtrlInput
    {
        public List<ViewInfo> Views { get; set; }
        public String DirectoryForViewCsFile { get; set; }
        public String LocationOfSqlMetalClasses { get; set; }
    }

    public class ViewCodeGeneratorController
    {
        private const string _folderLocForAssemblies = "SqlToLinqConverter";

        public void GenerateClassForView(ViewCodeGenCtrlInput inputs)
        {
            String assemblyLocation;
            GeneratedLinqInfo sqlToLinqConverterOutput;

            ////if there are no views, return immediately
            //if (inputs.Views.Count == 0)
            //    return;

            //Manage the folder that contains assemblies used by tool for reflection.
            ManageFolderWithAssemblies();

            //Step 1. Create assembly containing SqlMetal Classes.
            assemblyLocation = GenerateAssemblyFromSqlMetalClasses(inputs);

            if (inputs.Views != null) 
            {
                foreach (ViewInfo view in inputs.Views)
                {
                    //Step 2. Convert SQL Query to LINQ.
                    sqlToLinqConverterOutput = ConvertSqlToLinq(assemblyLocation, view);

                    //Step 3. Generate Class containing LINQ Query.
                    if (sqlToLinqConverterOutput != null)
                    {
                        GenerateClassFile(assemblyLocation, inputs, view, sqlToLinqConverterOutput);
                    }
                }
            }
        }

        private void GenerateClassFile(string assemblyLocation, ViewCodeGenCtrlInput inputs, ViewInfo currentView, GeneratedLinqInfo sqlToLinqConverterOutput)
        {
            ViewCodeGenerator codeGen;
            ViewCodeGeneratorInputs codeGenInputs;

            codeGenInputs = new ViewCodeGeneratorInputs()
            {
                ClassLocation = inputs.DirectoryForViewCsFile,
                LinqQuery = sqlToLinqConverterOutput,
                NameOfView = currentView.ViewName,
                SqlMetalCodeAssemblyLoc = assemblyLocation
            };

            codeGen = new ViewCodeGenerator();
            codeGen.GenerateClass(codeGenInputs);
        }

        private GeneratedLinqInfo ConvertSqlToLinq(String assemblyLocation, ViewInfo currentView)
        {
            bool fQuotedIdenfifiers;
            TSql100Parser parser;
            TSqlScript script;
            IScriptFragment fragment;
            IList<ParseError> errors;
            StringReader sqlReader;
            BuildLinqForSelectStatement linqBuilder;
            TableSchemaHelper tableSchemaHelper;
            GeneratedLinqInfo sqlToLinqConverterOutput = null;
            Errors conversionErrors;

            Console.WriteLine("===========================================");
            Console.WriteLine("Converting view - " + currentView.ViewName);

            tableSchemaHelper = GetTableSchemaHelperInst(assemblyLocation);

            fQuotedIdenfifiers = false;
            parser = new TSql100Parser(fQuotedIdenfifiers);

            sqlReader = new StringReader(currentView.ViewQuery);
            fragment = parser.Parse(sqlReader, out errors);

            script = fragment as TSqlScript;

            foreach (TSqlBatch sqlBatch in script.Batches)
            {
                foreach (TSqlStatement sqlQuery in sqlBatch.Statements)
                {
                    if (sqlQuery is SelectStatement)
                    {
                        linqBuilder = new BuildLinqForSelectStatement();
                        sqlToLinqConverterOutput = linqBuilder.GenerateLinq(sqlQuery as SelectStatement, tableSchemaHelper, out conversionErrors);

                        if (sqlToLinqConverterOutput == null) 
                        {
                            Console.WriteLine("Could not convert view - " + currentView.ViewName);
                            if (conversionErrors != null && conversionErrors.ErrorMessages != null) 
                            {
                                foreach (String error in conversionErrors.ErrorMessages)
                                {
                                    Console.WriteLine("Errors were - " + error);
                                }
                            }
                        }
                    }
                }
            }
            Console.WriteLine("===========================================");
            return sqlToLinqConverterOutput;
        }

        private String GenerateAssemblyFromSqlMetalClasses(ViewCodeGenCtrlInput inputs)
        {
            String nameOfAssembly;
            CSharpCodeProvider codeProvider;
            CompilerParameters compilerParams;

            codeProvider = new CSharpCodeProvider();
            compilerParams = new CompilerParameters();
            compilerParams.ReferencedAssemblies.Add("System.dll");
            compilerParams.ReferencedAssemblies.Add("System.Data.dll");
            compilerParams.ReferencedAssemblies.Add("System.Core.dll");
            compilerParams.ReferencedAssemblies.Add("System.Data.DataSetExtensions.dll");
            compilerParams.ReferencedAssemblies.Add("System.Data.Linq.dll");

            compilerParams.GenerateExecutable = false;
            compilerParams.GenerateInMemory = false;

            nameOfAssembly = String.Concat(Guid.NewGuid().ToString() + ".dll");
            //Store the dll for sql metal at temporary location.
            compilerParams.OutputAssembly = Path.Combine(Path.GetTempPath(), _folderLocForAssemblies, nameOfAssembly);

            codeProvider.CompileAssemblyFromFile(compilerParams, new String[] { inputs.LocationOfSqlMetalClasses });
            return compilerParams.OutputAssembly;
        }

        private TableSchemaHelper GetTableSchemaHelperInst(String locationOfAssembly)
        {
            TableSchemaHelper tableSchemaHelper;

            tableSchemaHelper = new TableSchemaHelper();
            tableSchemaHelper.Initialize(locationOfAssembly);

            return tableSchemaHelper;
        }

        private void ManageFolderWithAssemblies()
        {
            String temporaryFolderLoc;

            //Check if tool has created folder to save temporary DLL's
            temporaryFolderLoc = Path.Combine(Path.GetTempPath(), _folderLocForAssemblies);
            if (Directory.Exists(temporaryFolderLoc) == false)
            {
                Directory.CreateDirectory(temporaryFolderLoc);
            }

            //Clean up directory.
            DeleteAllFilesInDir(temporaryFolderLoc);
        }

        private void DeleteAllFilesInDir(String dirLocation)
        {
            String[] filesInDir;

            filesInDir = Directory.GetFiles(dirLocation);
            if (filesInDir != null && filesInDir.Count() != 0)
            {
                foreach (String filePath in filesInDir)
                {
                    File.Delete(filePath);
                }
            }
        }
    }
}
